跳到主要内容

多语言 Markdown/MDX 翻译流水线

一个关键要求是,通过确保仅发送有效的、人类可读的文本进行翻译,防止 LLM 产生幻觉或损坏代码和结构化内容。在不破坏代码、JSX、数学公式或元数据的情况下,将 Markdown 和 MDX 文件中的人类可读内容翻译,同时尽可能保留格式和结构。


核心问题

为了安全地使用 LLM 翻译文档,我们必须屏蔽或排除不可翻译的内容(如代码、JSX、数学公式和元数据)。将文档视为纯文本是不安全的,因为 LLM 可能会:

  • 产生代码幻觉

  • 翻译标识符

  • 破坏 JSX 或 Markdown 结构

  • 损坏 frontmatter 或元数据

因此,必须将文档解析为 AST(抽象语法树),以便根据节点类型选择性地应用翻译。

MDX + Docusaurus 兼容性

该项目的主要目标之一是与 Docusaurus 兼容,Docusaurus 使用 MDX(Markdown + JSX 组件)。

这给项目带来了很大的约束,因为由于需要支持 JSX、ESM 导入/导出以及嵌入的 JavaScript 表达式,在 Python 或 C++ 中还没有成熟且完全正确的 MDX 解析器。

虽然有一些 Python 库,如 tree-sitter,用于 Markdown,但它们通常不支持 MDX,并且无法安全地解析 Markdown 中的 JSX。

解决方案: 使用 unified.js 通过子进程

Docusaurus 本身使用 [unified.js](https://unifiedjs.com/),这是一个 JavaScript 生态系统,它提供了许多语言(包括 Markdown、MDX(与 JSX + ESM 配合)、GFM(表格、任务列表等)、frontmatter、Latex Math 和其他扩展)的解析、AST 转换(屏蔽)和字符串化功能。

鉴于 unified/remark 是 MDX 的行业标准解决方案,最终的设计决策是使用 JS unified.js 作为一个子进程,从 Python 中解析和转换 MD/MDX 文件。

为什么选择基于 AST 的屏蔽

这可以防止 LLM 产生幻觉和损坏代码/结构。不可翻译的内容通过排除 AST 节点(按节点类型)隐式地被屏蔽,而不是依赖启发式或正则表达式。 这样可以确保原始文档结构得以保留,同时仅翻译自然语言文本,而不会错误地将代码和 JSX 视为可翻译的文本。

备注

虽然整个流水线可以完全在 JavaScript 中实现(并且可能更简单),但由于该项目与现有的 Python 体系集成,因此专门使用 JS 用于解析和基于 AST 的屏蔽。

架构

Python (协调器)

  • 读取 Markdown/MDX 文件

  • 调用 Node.js 子进程 (或长生命周期工作进程)

  • 通过 stdin 发送文档文本

  • 从 stdout 接收转换后的 Markdown/MDX

  • 将输出写入磁盘

  • 处理批量、路由和与现有 Python 系统的集成

Node.js (解析器 + AST 转换器)

  • 使用 unified / remark 生态系统:

    • 将 Markdown + MDX 解析为真实的 AST

    • 遍历 AST

    • 屏蔽 / 跳过不可翻译的节点

    • 仅翻译安全的文本节点

    • 将 AST 字符串化为 Markdown/MDX

  • 确保:

    • 最佳的 MDX + JSX 支持

    • 正确处理 GFM 和 frontmatter

    • 安全、结构感知翻译

JS 堆栈

  • 核心堆栈 (Node.js)

  • unified

  • remark-parse

  • remark-mdx

  • remark-gfm

  • remark-frontmatter

  • remark-stringify

  • unist-util-visit-parents

  • LLM 后端 (例如,Ollama)

替代方案 (未选择)

选项原因
Tree-sitter适用于多语言解析,但对 MDX 和 JSX 敏感的 Markdown 的打印和回程支持不足
Pandoc/Panflute适用于 Markdown,但对 MDX + JSX 较弱
纯 Python 解析器没有真实的 MDX 支持
正则表达式 / 文本启发式不安全;会导致幻觉和文档损坏